package com.judysen.jsqlweb.utils;

import com.judysen.jsqlweb.exceptions.ServiceException;
import com.judysen.jsqlweb.filters.ThreadContext;
import com.judysen.jsqlweb.models.DatabaseConfigModel;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实现AbstractDataSource,实现动态数据库
 *
 */
public class JsqlDynamicDatasource extends AbstractDataSource {
    public JsqlDynamicDatasource(String databaseUrl, String userName, String password) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(databaseUrl);
        config.setUsername(userName);
        config.setPassword(password);
        var defaultDatabase = new HikariDataSource(config);
        this.setDatabase("default_web", defaultDatabase);      //设置一个默认的，防止启动时出错
    }

    private static ConcurrentHashMap<String, DataSource> resolvedDataSources = new ConcurrentHashMap<>(16);
    private static ConcurrentHashMap<String, DatabaseConfigModel> configs = new ConcurrentHashMap<>(16);

    /**
     * <p>Attempts to establish a connection with the data source that
     * this {@code DataSource} object represents.
     *
     * @return a connection to the data source
     * @throws SQLException        if a database access error occurs
     * @throws SQLTimeoutException when the driver has determined that the
     *                             timeout value specified by the {@code setLoginTimeout} method
     *                             has been exceeded and has at least tried to cancel the
     *                             current database connection attempt
     */
    @Override
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

    /**
     * <p>Attempts to establish a connection with the data source that
     * this {@code DataSource} object represents.
     *
     * @param username the database user on whose behalf the connection is
     *                 being made
     * @param password the user's password
     * @return a connection to the data source
     * @throws SQLException        if a database access error occurs
     * @throws SQLTimeoutException when the driver has determined that the
     *                             timeout value specified by the {@code setLoginTimeout} method
     *                             has been exceeded and has at least tried to cancel the
     *                             current database connection attempt
     * @since 1.4
     */
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return this.determineTargetDataSource().getConnection(username, password);
    }

    /**
     * 设置数据源
     *
     * @param databaseKey
     * @param database
     */
    public synchronized void setDatabase(String databaseKey, DataSource database) {
        if (!resolvedDataSources.containsKey(databaseKey)) {
            resolvedDataSources.put(databaseKey, database);
        }
    }

    /**
     * 数据库连接
     * @param configModel
     */
    public synchronized void setDatabaseByConfig(DatabaseConfigModel configModel) {
        List<DatabaseConfigModel> models = new ArrayList<>();
        models.add(configModel);

        this.setAllDatabase(models);
    }

    /**
     * 设置所有的数据库配置
     * @param configModelList
     */
    public void setAllDatabase(List<DatabaseConfigModel> configModelList) {
        configModelList.forEach(f -> {
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl(f.getJdbcUrl());
            config.setUsername(f.getUserName());
            config.setPassword(f.getPassword());
            var database = new HikariDataSource(config);
            configs.put(f.getDisplayName(), f);
            this.setDatabase(f.getDisplayName(), database);
        });
    }

    /**
     * @return
     */
    protected Object determineCurrentLookupKey() {
        var key = ThreadContext.getDatabaseName();
        if (StringUtils.isEmpty(key)) {
            key = "default_web";
        }
        return key;
    }

    /**
     * @return
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource) resolvedDataSources.get(lookupKey);

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }

    /**
     * 删除数据库连接
     *
     * @param key
     */
    public synchronized void removeDatabase(Object key) {
        if (resolvedDataSources.containsKey(key)) {
            var database = (DataSource) resolvedDataSources.remove(key);
            try {
                database.getConnection().close();
                configs.remove(key);
            } catch (SQLException ex) {
                logger.warn(String.format("%s 关闭数据库失败!", key));
            }
        }
    }

    public static DatabaseConfigModel getCurrentDb() {
        var dbDisplayName = ThreadContext.getDatabaseName();
        if (configs.size() < 1) {
            throw new ServiceException(500, "没有数据库");
        }
        return configs.getOrDefault(dbDisplayName,null);
    }
}
